# 完整跑仿真程序

import numpy as np
import triangle
import numpy as np
from shapely.geometry import Polygon
import matplotlib.pyplot as plt
import os
import numpy as np
import sys
sys.path.append(r"C:\Reaserch\CSTStudioSuite2021\AMD64\python_cst_libraries")
import cst.interface
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
import trimesh
import plotly.graph_objects as go
from tqdm import tqdm
line_break = '\n'#换行符，后面用于VBA代买的拼接用


# 生成一个随机的二维多边形
def generate_random_2d_polygon(num_vertices=6):
    #points = np.random.rand(num_vertices, 2)
    points = np.random.uniform(-19, 19,size=(num_vertices, 2))
    center = np.mean(points, axis=0)
    angles = np.arctan2(points[:, 1] - center[1], points[:, 0] - center[0])
    points = points[np.argsort(angles)]
    return points

def generate_polygon_with_fixed_edges2(fixed_edges, num_random_vertices=4):
    # 提取固定边的顶点
    fixed_points = np.vstack(fixed_edges)
    
    # 在 -19 到 19 之间生成间隔为 0.1 的点
    x_values = np.arange(-24.7, 24.7, 5)
    y_values = np.arange(-20.5, 20.5, 5)
    
    # 随机选择 num_random_vertices 个点
    random_points = []
    while len(random_points) < num_random_vertices:
        # 随机选择 x 和 y 坐标
        x = np.random.choice(x_values)
        y = np.random.choice(y_values)
        point = np.array([x, y])
        random_points.append(point)
    
    random_points = np.array(random_points)
    
    # 将固定的点和随机点合并在一起
    all_points = np.vstack([fixed_points, random_points])
    
    # 计算所有点的中心点
    center = np.mean(all_points, axis=0)
    
    # 按照与中心点的角度对所有点进行排序（确保按逆时针方向排列）
    angles = np.arctan2(all_points[:, 1] - center[1], all_points[:, 0] - center[0])
    sorted_points = all_points[np.argsort(angles)]
    
    # 使用 shapely 生成 Polygon
    polygon = Polygon(sorted_points)
    
    return polygon, sorted_points


# 生成包含已知边的随机二维多边形（y坐标要大于0.4或者小于0.3）
def generate_polygon_with_fixed_edges(fixed_edges, num_random_vertices=4):
    # 提取固定边的顶点
    fixed_points = np.vstack(fixed_edges)
    
    # 随机生成其余的点，满足 y > 0.4 或 y < 0.3
    random_points = []
    while len(random_points) < num_random_vertices:
        #point = np.random.rand(2)  # 生成一个随机点
        point = np.random.uniform(-19, 19,size=2)
        #if point[1] > 0.4 or point[1] < 0.3:  # 仅保留 y 坐标符合条件的点
        random_points.append(point)
    random_points = np.array(random_points)
    
    # 将固定的点和随机点合并在一起
    all_points = np.vstack([fixed_points, random_points])
    
    # 计算所有点的中心点
    center = np.mean(all_points, axis=0)
    
    # 按照与中心点的角度对所有点进行排序（确保按逆时针方向排列）
    angles = np.arctan2(all_points[:, 1] - center[1], all_points[:, 0] - center[0])
    sorted_points = all_points[np.argsort(angles)]
    
    # 使用 shapely 生成 Polygon
    polygon = Polygon(sorted_points)
    
    return polygon, sorted_points
# 对多边形进行三角剖分
def constrained_triangulation(polygon_points):
    vertices = np.array(polygon_points)
    # 定义约束边，保持多边形的边界不被破坏
    segments = np.array([[i, (i + 1) % len(vertices)] for i in range(len(vertices))])
    
    # 使用 triangle 库进行带约束的三角剖分
    A = dict(vertices=vertices, segments=segments)
    triangulation = triangle.triangulate(A, 'p')
    
    return triangulation

# 将三角剖分结果保存为OBJ文件
def export_to_obj(triangulation, filename="polygon_triangulated.obj"):
    vertices = triangulation['vertices']
    triangles = triangulation['triangles']
    
    with open(filename, 'w') as f:
        # 写入顶点信息
        for v in vertices:
            f.write(f"v {v[0]} {v[1]} 0.0\n")  # z 设为 0 表示在二维平面上
        
        # 写入面（每个面由3个顶点构成）
        for tri in triangles:
            # OBJ 索引从1开始，因此要加1
            f.write(f"f {tri[0] + 1} {tri[1] + 1} {tri[2] + 1}\n")


# 加载OBJ文件并转换为Plotly格式
def plot_obj_in_jupyter(obj_filename):
    # 使用trimesh加载OBJ文件
    mesh = trimesh.load(obj_filename)
    
    # 获取顶点和面数据
    vertices = mesh.vertices
    faces = mesh.faces
    
    # 创建Plotly图形对象
    mesh_plot = go.Mesh3d(
        x=vertices[:, 0],
        y=vertices[:, 1],
        z=vertices[:, 2],
        i=faces[:, 0],
        j=faces[:, 1],
        k=faces[:, 2],
        opacity=0.5,
        color='lightblue'
    )
    
    # 设置布局
    layout = go.Layout(
        scene=dict(
            xaxis=dict(visible=False),
            yaxis=dict(visible=False),
            zaxis=dict(visible=False)
        ),
        title="OBJ File Visualization"
    )
    
    # 创建并展示图像
    fig = go.Figure(data=[mesh_plot], layout=layout)
    fig.show()

def cst_init(fullname,modeler,mws):
    # path = os.getcwd()#获取当前py文件所在文件夹
    # filename = 'Patch_Antenna.cst'
    # fullname = os.path.join(path,filename)
    print(fullname)

    # project = cst.interface.DesignEnvironment()
    # project.set_quiet_mode(False)
    # mws = project.new_mws()
    mws.save(fullname)
    # modeler = mws.modeler
    #贴片天线建模基本参数
    a = 38.6 #贴片长
    b = 38 #贴片宽
    w = 1.46 #馈线宽，100欧姆传输线
    l = 40 #馈线长
    lx = 100 #基板长
    ly = 100 #基板宽
    ts = 2 #基板厚
    tm = 0.035 #金属层厚
    Frq = [2,10] #工作频率，单位：GHz

    #在CST中加入结构参数，方便后续手动在CST文件中进行操作
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("a", "%f")' % a)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("b", "%f")' % b)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("w", "%f")' % w)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("l", "%f")' % l)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("lx", "%f")' % lx)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("ly", "%f")' % ly)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("ts", "%f")' % ts)
    modeler.add_to_history('StoreParameter','MakeSureParameterExists("tm", "%f")' % tm)
    #建模基本参数设置结束
    line_break = '\n'#换行符，后面用于VBA代买的拼接用
    #全局单位初始化
    sCommand = ['With Units',
                '.Geometry "mm"',
                '.Frequency "ghz"',
                '.Time "ns"',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define units', sCommand)
    #全局单位初始化结束

    #工作频率设置
    sCommand = 'Solver.FrequencyRange "%f","%f"'  % (Frq[0],Frq[1])
    modeler.add_to_history('define frequency range', sCommand)
    #工作频率设置结束

    #背景材料设置
    sCommand = ['With Background',
                '.ResetBackground',
                '.Type "Normal"',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define background', sCommand)
    #背景材料设置结束

    #边界条件设置。
    sCommand = ['With Boundary',
                '.Xmin "expanded open"',
                '.Xmax "expanded open"',
                '.Ymin "expanded open"',
                '.Ymax "expanded open"',
                '.Zmin "expanded open"',
                '.Zmax "expanded open"',
                '.Xsymmetry "none"',
                '.Ysymmetry "none"',
                '.Zsymmetry "none"',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define boundary', sCommand)
    #边界条件设置结束

    #新建所需介质材料
    er1 = 2.65
    sCommand = ['With Material',
                '.Reset',
                '.Name "material1"',
                '.FrqType "all"',
                '.Type "Normal"',
                '.Epsilon %f' %er1,
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define material: material265', sCommand)
    #新建所需介质材料结束

    #使Bounding Box显示
    sCommand = 'Plot.DrawBox "True"'
    modeler.add_to_history('switch bounding box', sCommand)
    #使Bounding Box显示结束

    sCommand = 'Plot.ZoomToStructure'
    modeler.add_to_history('ZoomToStructure', sCommand)#缩放到适合大小，就和在CST里面按空格是一个效果

    Str_Name='line1'
    Str_Component='Feed'
    Str_Material='PEC'
    sCommand = ['With Brick',
                '.Reset',
                '.Name "%s"' % Str_Name,
                '.Component "%s"' % Str_Component,
                '.Material "%s"' % Str_Material,
                '.Xrange "-lx/2","-a/2"',
                '.Yrange "-w/2","w/2"',
                '.Zrange "0","tm"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand) 

    Str_Name='line2'
    Str_Component='Feed'
    Str_Material='PEC'
    sCommand = ['With Brick',
                '.Reset',
                '.Name "%s"' % Str_Name,
                '.Component "%s"' % Str_Component,
                '.Material "%s"' % Str_Material,
                '.Xrange "a/2","lx/2"',
                '.Yrange "-w/2","w/2"',
                '.Zrange "0","tm"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand)  
    Str_Name='bottom'
    Str_Component='Bottom'
    Str_Material='PEC'
    sCommand = ['With Brick',
                '.Reset',
                '.Name "%s"' % Str_Name,
                '.Component "%s"' % Str_Component,
                '.Material "%s"' % Str_Material,
                '.Xrange "-lx/2","lx/2"',
                '.Yrange "-ly/2","ly/2"',
                '.Zrange "-ts-tm","-ts"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand) 





    Str_Name='sub'
    Str_Component='Sub'
    Str_Material='material1'
    sCommand = ['With Brick',
                '.Reset',
                '.Name "%s"' % Str_Name,
                '.Component "%s"' % Str_Component,
                '.Material "%s"' % Str_Material,
                '.Xrange "-lx/2","lx/2"',
                '.Yrange "-ly/2","ly/2"',
                '.Zrange "-ts","0"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('define brick:%s:%s' % (Str_Component,Str_Name,), sCommand)
    #建模结束

    sCommand = 'Plot.ZoomToStructure'
    modeler.add_to_history('ZoomToStructure', sCommand)#缩放到适合大小，就和在CST里面按空格是一个效果

    #端口设置，采用的方法和在CST里面选中一个面然后设置端口是一样的操作，这里完全复现
    #端口1
    sCommand = 'Pick.PickFaceFromId "Feed:line1",4'
    modeler.add_to_history('pick face', sCommand)
    sCommand = ['With Port',
                '.Reset',
                '.PortNumber 1',
                '.Label  ""',
                '.NumberOfModes 1',
                '.AdjustPolarization "False"',
                '.PolarizationAngle 0.0',
                '.ReferencePlaneDistance 0',
                '.TextSize 50',
                '.TextMaxLimit 0',
                '.Coordinates "Picks"',
                '.Orientation "positive"',
                '.PortOnBound "False"',
                '.ClipPickedPortToBound "False"',
                '.Xrange "-lx/2","-lx/2"',
                '.Yrange "-w/2","w/2"',
                '.Zrange "0","tm"',
                '.XrangeAdd "0.0","0.0"',
                '.YrangeAdd "3*ts","3*ts"',
                '.ZrangeAdd "ts","3*ts"',
                '.SingleEnded "False"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)     
    modeler.add_to_history('define port1', sCommand)

    #端口2
    sCommand = 'Pick.PickFaceFromId "Feed:line2",6'
    modeler.add_to_history('pick face', sCommand)
    sCommand = ['With Port',
                '.Reset',
                '.PortNumber 2',
                '.Label  ""',
                '.NumberOfModes 1',
                '.AdjustPolarization "False"',
                '.PolarizationAngle 0.0',
                '.ReferencePlaneDistance 0',
                '.TextSize 50',
                '.TextMaxLimit 0',
                '.Coordinates "Picks"',
                '.Orientation "positive"',
                '.PortOnBound "False"',
                '.ClipPickedPortToBound "False"',
                '.Xrange "lx/2","lx/2"',
                '.Yrange "-w/2","w/2"',
                '.Zrange "0","tm"',
                '.XrangeAdd "0.0","0.0"',
                '.YrangeAdd "3*ts","3*ts"',
                '.ZrangeAdd "ts","3*ts"',
                '.SingleEnded "False"',
                '.Create',
                'End With']
    sCommand = line_break.join(sCommand)     
    modeler.add_to_history('define port2', sCommand)
    #端口设置结束
    # 原版的
    # #设置远场方向图的Monitor
    # sCommand = ['With Monitor',
    #             '.Reset',
    #             '.Domain "Frequency"',
    #             '.FieldType "Farfield"',
    #             #'.ExportFarfieldSource "False"',
    #             '.ExportFarfieldSource "True"',
    #             '.UseSubvolume "False"',
    #             '.Coordinates "Picks"',
    #             '.SetSubvolume "50", "50", "-0.73", "0.73", "0", "0.035"',
    #             '.SetSubvolumeOffset "10", "10", "10", "10", "10", "10" ',
    #             '.SetSubvolumeInflateWithOffset "False" ',
    #             '.SetSubvolumeOffsetType "FractionOfWavelength" ',
    #             '.EnableNearfieldCalculation "True" ',
    #             '.CreateUsingLinearStep "%f", "%f", "%f"' % (Frq[0],Frq[1],0.05),
    #             'End With']
    # sCommand = line_break.join(sCommand) 
    # modeler.add_to_history('define farfield monitor (using linear step)',sCommand)
    # #设置远场方向图的Monitor结束



    # 程序里面的
    # 设置远场方向图的Monitor
    sCommand = ['With Monitor',
                '.Reset',
                '.Domain "Frequency"',
                '.FieldType "Farfield"',
                #'.ExportFarfieldSource "False"',
                '.ExportFarfieldSource "True"',
                '.UseSubvolume "False"',
                '.Coordinates "Structure"',
                '.SetSubvolume "-50", "50", "-50", "50", "-2.035", "6.035"',
                '.SetSubvolumeOffset "0.0", "0.0", "0.0", "0.0", "0.0", "0.0"',
                '.SetSubvolumeInflateWithOffset "False" ',
                '.CreateUsingLinearStep "%f", "%f", "%f"' % (Frq[0],Frq[1],0.2),
                'End With']
    sCommand = line_break.join(sCommand) 
    modeler.add_to_history('define farfield monitor (using linear step)',sCommand)
    #设置远场方向图的Monitor结束

# With Monitor
#           .Reset 
#           .Domain "Frequency"
#           .FieldType "Efield"
#           .Dimension "Volume" 
#           .UseSubvolume "False" 
#           .Coordinates "Structure" 
#           .SetSubvolume "-50", "50", "-50", "50", "-2.035", "6.035" 
#           .SetSubvolumeOffset "0.0", "0.0", "0.0", "0.0", "0.0", "0.0" 
#           .SetSubvolumeInflateWithOffset "False" 
#           .CreateUsingLinearStep "2", "10", "0.9"
# End With


# Mesh.SetCreator "High Frequency" 

# With Solver 
#      .Method "Hexahedral"
#      .CalculationType "TD-S"
#      .StimulationPort "All"
#      .StimulationMode "All"
#      .SteadyStateLimit "-40"
#      .MeshAdaption "False"
#      .AutoNormImpedance "False"
#      .NormingImpedance "50"
#      .CalculateModesOnly "False"
#      .SParaSymmetry "False"
#      .StoreTDResultsInCache  "False"
#      .FullDeembedding "False"
#      .SuperimposePLWExcitation "False"
#      .UseSensitivityAnalysis "False"
# End With

    #求解
    sCommand = ['Mesh.SetCreator "High Frequency" ',
                'With Solver',
                '.Method "Hexahedral"',
                '.CalculationType "TD-S"',
                '.StimulationPort "All"',
                '.StimulationMode "All"',
                '.SteadyStateLimit "-40"',
                '.MeshAdaption "False"',
                '.AutoNormImpedance "False"',
                '.NormingImpedance "50"',
                '.CalculateModesOnly "False"',
                '.SParaSymmetry "False"',
                #'.StoreTDResultsInCache  "False"',
                '.StoreTDResultsInCache  "True"',
                '.FullDeembedding "False"',
                '.SuperimposePLWExcitation "False"',
                '.UseSensitivityAnalysis "False"',         
                'End With']
    sCommand = line_break.join(sCommand) 
    modeler.add_to_history('define Mesh.SetCreator',sCommand)

    # # 设置成极坐标
    # sCommand =  ['With FarfieldPlot',
    #     '.Plottype "Polar"',
    #     '.Vary "angle1"',
    #     '.Theta "90"',
    #     '.Phi "90"' ,
    #     '.Step "1"',
    #     '.Step2 "1"',
    #     '.SetLockSteps "True"',
    #     '.SetPlotRangeOnly "False"',
    #     '.SetThetaStart "0"',
    #     '.SetThetaEnd "180"' ,
    #     '.SetPhiStart "0"' ,
    #     '.SetPhiEnd "360"' ,
    #     '.SetTheta360 "False"' ,
    #     '.SymmetricRange "False"' ,
    #     '.SetTimeDomainFF "False"' ,
    #     '.SetFrequency "-1"' ,
    #     '.SetTime "0"' ,
    #     '.SetColorByValue "True"' ,
    #     '.DrawStepLines "False"' ,
    #     '.DrawIsoLongitudeLatitudeLines "False"' ,
    #     '.ShowStructure "False"' ,
    #     '.ShowStructureProfile "False"' ,
    #     '.SetStructureTransparent "False"' ,
    #     '.SetFarfieldTransparent "False"' ,
    #     '.AspectRatio "Free"' ,
    #     '.ShowGridlines "True"' ,
    #     '.SetSpecials "enablepolarextralines"' ,
    #     '.SetPlotMode "Directivity"' ,
    #     '.Distance "1"' ,
    #     '.UseFarfieldApproximation "True"' ,
    #     '.IncludeUnitCellSidewalls "True"' ,
    #     '.SetScaleLinear "False"' ,
    #     '.SetLogRange "40"' ,
    #     '.SetLogNorm "0"' ,
    #     '.DBUnit "0"' ,
    #     '.SetMaxReferenceMode "abs"' ,
    #     '.EnableFixPlotMaximum "False"' ,
    #     '.SetFixPlotMaximumValue "1"' ,
    #     '.SetInverseAxialRatio "False"' ,
    #     '.SetAxesType "user"' ,
    #     '.SetAntennaType "unknown"',
    #     '.Phistart "1.000000e+00", "0.000000e+00", "0.000000e+00"' ,
    #     '.Thetastart "0.000000e+00", "0.000000e+00", "1.000000e+00"' ,
    #     '.PolarizationVector "0.000000e+00", "1.000000e+00", "0.000000e+00"' ,
    #     '.SetCoordinateSystemType "spherical"' ,
    #     '.SetAutomaticCoordinateSystem "True"' ,
    #     '.SetPolarizationType "Linear"' ,
    #     '.SlantAngle 0.000000e+00' ,
    #     '.Origin "bbox"' ,
    #     '.Userorigin "0.000000e+00", "0.000000e+00", "0.000000e+00"' ,
    #     '.SetUserDecouplingPlane "False"' ,
    #     '.UseDecouplingPlane "False"' ,
    #     '.DecouplingPlaneAxis "X"' ,
    #     '.DecouplingPlanePosition "0.000000e+00"' ,
    #     '.LossyGround "False"' ,
    #     '.GroundEpsilon "1"' ,
    #     '.GroundKappa "0"' ,
    #     '.EnablePhaseCenterCalculation "False"' ,
    #     '.SetPhaseCenterAngularLimit "3.000000e+01"' ,
    #     '.SetPhaseCenterComponent "boresight"' ,
    #     '.SetPhaseCenterPlane "both"' ,
    #     '.ShowPhaseCenter "True"' ,
    #     '.ClearCuts' ,
    #     '.StoreSettings',
    #     'End With']

    # sCommand = line_break.join(sCommand)
    # modeler.add_to_history('FarfieldPlot Polar', sCommand)

def cst_import_obj(modeler,file_path):
    #file_path="C:\\Reaserch\\Code\\CEM\\AntennaGPT\\code\\polygon_triangulated3.obj"
    Str_Name='patch'
    Str_Component='Patch'
    sCommand = ['With OBJ',
                '.Reset',
                '.FileName ("%s")' % file_path,
                '.Name ("test")',
                '.Layer ("Patch")',
                '.ScaleFactor ("1")',
                '.ImportToActiveCoordinateSystem (False)',
                '.Read',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('Import OBJ File', sCommand)
    # pick一个face
    sCommand = 'Pick.PickFaceFromId "Patch:test",0'
    modeler.add_to_history('pick face Patch:test', sCommand)

    #  Extrude Face
    sCommand = ['With Extrude',
            '.Reset',
            '.Name ("solid2")',
            '.Component ("component1")',
            '.Material ("PEC")',
            '.Mode ("Picks")',
            '.Height ("tm")',
            '.Taper (5)',
            '.UsePicksForHeight (False)',
            '.DeleteBaseFaceSolid (False)',
            '.ClearPickedFace (True)',
            '.Create',
            'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('Extrude Component ("component1")', sCommand)
def cst_delete_compo(modeler):
    sCommand = 'Component.Delete "component1"'
    modeler.add_to_history('Delete component1', sCommand)
    sCommand = 'Component.Delete "Patch"'
    modeler.add_to_history('Delete Patch', sCommand)
def cst_run(modeler,mws,fullname):
    #仿真开始
    modeler.run_solver()
    #仿真结束

    mws.save(fullname)#保存
def cst_export_s(modeler,file_path):
    # 导出
    sCommand = ['With TOUCHSTONE',
                '.Reset',
                '.FileName ("%s")' %(file_path),
                '.Impedance (100)',
                '.FrequencyRange ("Full")',
                '.Renormalize (False)',
                '.UseARResults (False)',
                '.SetNSamples (1001)',
                '.Format ("DB")',
                '.Write',
                'End With']
    sCommand = line_break.join(sCommand)
    modeler.add_to_history('Export TOUCHSTONE s', sCommand)


def cst_export_farfiled(i):

    for j in range(200,1100,5):
        # 导出
        sCommand =  ['SelectTreeItem ("Farfields\farfield（f=%.2f）[1]")' % j,
                    'With ASCIIExport',
                    '.Reset',
                    '.FileName ("C:\Reaserch\Code\CEM\AntennaGPT\data\Farfiled\data%s\example.txt")' % str(j),
                    '.Mode ("FixedNumber")',
                    '.Execute',
                    'End With']

        sCommand = line_break.join(sCommand)
        modeler.add_to_history('Export farfiled ASCII', sCommand) 


def generate_polygon_with_external_fixed_edge(fixed_edge, num_points=20):
    """
    随机生成一个多边形，固定边在多边形外部
    :param fixed_edge: 固定边的两个顶点，例如 [(x1, y1), (x2, y2)]
    :param num_points: 除固定边外的随机顶点数量
    """
    # 转换固定边为 NumPy 数组
    fixed_edge = np.array(fixed_edge)
    p1, p2 = fixed_edge[0], fixed_edge[1]
    
    # 计算固定边的方向和垂直方向
    edge_vector = p2 - p1
    perpendicular_vector = np.array([-edge_vector[1], edge_vector[0]])  # 垂直方向
    
    # 在固定边的外侧生成随机点
    random_points = []
    for _ in range(num_points):
        t = np.random.uniform(-0.2, 1.2)  # 延固定边方向稍微扩展范围
        random_point_on_edge = p1 + t * edge_vector  # 固定边上的随机点
        random_offset = - np.random.uniform(0.1, 1) * perpendicular_vector  # 外侧偏移
        random_points.append(random_point_on_edge + random_offset)
    
    random_points = np.array(random_points)
    
    # 合并固定边的两个点和随机生成的点
    all_points = np.vstack([fixed_edge, random_points])
    
    # 使用凸包算法生成多边形
    hull = ConvexHull(all_points)
    polygon_points = all_points[hull.vertices]  # 获取凸包顶点的坐标
    return polygon_points
# 主程序
if __name__ == "__main__":
    # 路径
    file='./data/Refpa/'

    path = os.getcwd()
    filename = '../cst/Refpa.cst'
    fullname = os.path.join(path,filename)

    # 假设有两条固定的边
    fixed_edge_1 = np.array([[-24.7, 20.5], [24.7, 20.5]])  # 第一条固定边
    #fixed_edge_2 = np.array([[-19.3, 0.73], [-19.3, -0.73]])  # 第二条固定边

    #polygon_points = generate_random_2d_polygon(num_vertices)

    # 生成一个随机多边形，包含固定的边，且随机点的y坐标满足要求
    # project = cst.interface.DesignEnvironment()
    # project.set_quiet_mode(False)
    # mws = project.new_mws()
    # modeler = mws.modeler

    # cst_init(fullname,modeler,mws)
    
    for i in tqdm(range(500)):
        
        file_obj=file+'data'+str(i)+'.obj'
        file_s2p=file+'data'+str(i)
        
        sorted_points=generate_polygon_with_external_fixed_edge(fixed_edge_1, num_points=20)
        #polygon, sorted_points = generate_polygon_with_fixed_edges2([fixed_edge_1], num_random_vertices=5)

        # 进行带约束的三角剖分
        triangulation = constrained_triangulation(sorted_points)
        
        # 导出为OBJ文件
        export_to_obj(triangulation,file_obj)
        
        print("OBJ 文件已生成："+file_obj)
        # 展示生成的 OBJ 文件
        #plot_obj_in_jupyter(file_obj)
        
        # cst_import_obj(modeler,os.path.abspath(file_obj))
        # cst_run(modeler,mws,fullname)
        # cst_export_s(modeler,os.path.abspath(file_s2p))
        # cst_delete_compo(modeler)
        
        
